package com.lyncc.netty.production.client.connector; import static com.lyncc.netty.production.common.NettyCommonProtocol.ACK; import static com.lyncc.netty.production.common.NettyCommonProtocol.MAGIC; import static com.lyncc.netty.production.common.NettyCommonProtocol.RESPONSE; import static com.lyncc.netty.production.common.NettyCommonProtocol.SERVICE_1; import static com.lyncc.netty.production.common.NettyCommonProtocol.SERVICE_2; import static com.lyncc.netty.production.common.NettyCommonProtocol.SERVICE_3; import static com.lyncc.netty.production.serializer.SerializerHolder.serializerImpl; import static java.util.concurrent.TimeUnit.SECONDS; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.DefaultMessageSizeEstimator; import io.netty.channel.EventLoopGroup; import io.netty.channel.epoll.EpollEventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.MessageToByteEncoder; import io.netty.handler.codec.ReplayingDecoder; import io.netty.handler.timeout.IdleStateHandler; import io.netty.util.HashedWheelTimer; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.lyncc.netty.production.ConnectionWatchdog; import com.lyncc.netty.production.common.Acknowledge; import com.lyncc.netty.production.common.Message; import com.lyncc.netty.production.common.NativeSupport; import com.lyncc.netty.production.common.NettyCommonProtocol; import com.lyncc.netty.production.common.exception.ConnectFailedException; import com.lyncc.netty.production.srv.acceptor.AcknowledgeEncoder; /** * * @author BazingaLyn * @description 默认的一些比较常用的client的配置 * @time 2016年7月22日14:54:37 * @modifytime */ public class DefaultCommonClientConnector extends NettyClientConnector { private static final Logger logger = LoggerFactory.getLogger(DefaultCommonClientConnector.class); //每个连接维护一个channel private volatile Channel channel; //信息处理的handler private final MessageHandler handler = new MessageHandler(); //编码 private final MessageEncoder encoder = new MessageEncoder(); //ack private final AcknowledgeEncoder ackEncoder = new AcknowledgeEncoder(); private final ConcurrentMap<Long, MessageNonAck> messagesNonAcks = new ConcurrentHashMap<Long, MessageNonAck>(); protected final HashedWheelTimer timer = new HashedWheelTimer(new ThreadFactory() { private AtomicInteger threadIndex = new AtomicInteger(0); public Thread newThread(Runnable r) { return new Thread(r, "NettyClientConnectorExecutor_" + this.threadIndex.incrementAndGet()); } }); //心跳trigger private final ConnectorIdleStateTrigger idleStateTrigger = new ConnectorIdleStateTrigger(); public DefaultCommonClientConnector() { init(); } @Override protected void init() { super.init(); bootstrap().option(ChannelOption.ALLOCATOR, allocator) .option(ChannelOption.MESSAGE_SIZE_ESTIMATOR, DefaultMessageSizeEstimator.DEFAULT) .option(ChannelOption.SO_REUSEADDR, true) .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, (int) SECONDS.toMillis(3)) .channel(NioSocketChannel.class); bootstrap().option(ChannelOption.SO_KEEPALIVE, true) .option(ChannelOption.TCP_NODELAY, true) .option(ChannelOption.ALLOW_HALF_CLOSURE, false); } public Channel connect(int port, String host) { final Bootstrap boot = bootstrap(); // 重连watchdog final ConnectionWatchdog watchdog = new ConnectionWatchdog(boot, timer, port,host) { public ChannelHandler[] handlers() { return new ChannelHandler[] { //将自己[ConnectionWatchdog]装载到handler链中,当链路断掉之后,会触发ConnectionWatchdog #channelInActive方法 this, //每隔30s的时间触发一次userEventTriggered的方法,并且指定IdleState的状态位是WRITER_IDLE new IdleStateHandler(0, 30, 0, TimeUnit.SECONDS), //实现userEventTriggered方法,并在state是WRITER_IDLE的时候发送一个心跳包到sever端,告诉server端我还活着 idleStateTrigger, new MessageDecoder(), encoder, ackEncoder, handler }; }}; watchdog.setReconnect(true); try { ChannelFuture future; synchronized (bootstrapLock()) { boot.handler(new ChannelInitializer<NioSocketChannel>() { @Override protected void initChannel(NioSocketChannel ch) throws Exception { ch.pipeline().addLast(watchdog.handlers()); } }); future = boot.connect("127.0.0.1", 20011); } future.sync(); channel = future.channel(); } catch (Throwable t) { throw new ConnectFailedException("connects to [" + host + ":"+port+"] fails", t); } return channel; } @ChannelHandler.Sharable class MessageHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if(msg instanceof Acknowledge){ logger.info("收到server端的Ack信息,无需再次发送信息"); messagesNonAcks.remove(((Acknowledge)msg).sequence()); } } } /** * ************************************************************************************************** * Protocol * ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ * 2 │ 1 │ 1 │ 8 │ 4 │ * ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤ * │ │ │ │ │ * │ MAGIC Sign Status Invoke Id Body Length Body Content │ * │ │ │ │ │ * └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ * * 消息头16个字节定长 * = 2 // MAGIC = (short) 0xbabe * + 1 // 消息标志位, 用来表示消息类型 * + 1 // 空 * + 8 // 消息 id long 类型 * + 4 // 消息体body长度, int类型 */ @ChannelHandler.Sharable static class MessageEncoder extends MessageToByteEncoder<Message> { @Override protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception { byte[] bytes = serializerImpl().writeObject(msg); out.writeShort(MAGIC) .writeByte(msg.sign()) .writeByte(0) .writeLong(0) .writeInt(bytes.length) .writeBytes(bytes); } } static class MessageDecoder extends ReplayingDecoder<MessageDecoder.State> { public MessageDecoder() { super(State.HEADER_MAGIC); } // 协议头 private final NettyCommonProtocol header = new NettyCommonProtocol(); @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { switch (state()) { case HEADER_MAGIC: checkMagic(in.readShort()); // MAGIC checkpoint(State.HEADER_SIGN); case HEADER_SIGN: header.sign(in.readByte()); // 消息标志位 checkpoint(State.HEADER_STATUS); case HEADER_STATUS: in.readByte(); // no-op checkpoint(State.HEADER_ID); case HEADER_ID: header.id(in.readLong()); // 消息id checkpoint(State.HEADER_BODY_LENGTH); case HEADER_BODY_LENGTH: header.bodyLength(in.readInt()); // 消息体长度 checkpoint(State.BODY); case BODY: switch (header.sign()) { case RESPONSE: case SERVICE_1: case SERVICE_2: case SERVICE_3: { byte[] bytes = new byte[header.bodyLength()]; in.readBytes(bytes); Message msg = serializerImpl().readObject(bytes, Message.class); msg.sign(header.sign()); out.add(msg); break; } case ACK: { byte[] bytes = new byte[header.bodyLength()]; in.readBytes(bytes); Acknowledge ack = serializerImpl().readObject(bytes, Acknowledge.class); out.add(ack); break; } default: throw new IllegalArgumentException(); } checkpoint(State.HEADER_MAGIC); } } private static void checkMagic(short magic) throws Exception { if (MAGIC != magic) { throw new IllegalArgumentException(); } } enum State { HEADER_MAGIC, HEADER_SIGN, HEADER_STATUS, HEADER_ID, HEADER_BODY_LENGTH, BODY } } @Override protected EventLoopGroup initEventLoopGroup(int nWorkers, ThreadFactory workerFactory) { return NativeSupport.isSupportNativeET() ? new EpollEventLoopGroup(nWorkers, workerFactory) : new NioEventLoopGroup(nWorkers, workerFactory); } public static class MessageNonAck { private final long id; private final Message msg; private final Channel channel; private final long timestamp = System.currentTimeMillis(); public MessageNonAck(Message msg, Channel channel) { this.msg = msg; this.channel = channel; id = msg.sequence(); } } private class AckTimeoutScanner implements Runnable { public void run() { for (;;) { try { for (MessageNonAck m : messagesNonAcks.values()) { if (System.currentTimeMillis() - m.timestamp > SECONDS.toMillis(10)) { // 移除 if (messagesNonAcks.remove(m.id) == null) { continue; } if (m.channel.isActive()) { logger.warn("准备重新发送信息"); MessageNonAck msgNonAck = new MessageNonAck(m.msg, m.channel); messagesNonAcks.put(msgNonAck.id, msgNonAck); m.channel.writeAndFlush(m.msg) .addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); } } } Thread.sleep(300); } catch (Throwable t) { logger.error("An exception has been caught while scanning the timeout acknowledges {}.", t); } } } } { Thread t = new Thread(new AckTimeoutScanner(), "ack.timeout.scanner"); t.setDaemon(true); t.start(); } public void addNeedAckMessageInfo(MessageNonAck msgNonAck) { messagesNonAcks.put(msgNonAck.id, msgNonAck); } }